import sdl2
from .gl_tools import *
import pyrr


def clamp(x, a, b):
    assert a <= b
    return min(b, max(a, x))

def lerp(p1, p2, t):
    t = clamp(t, 0, 1)
    return p1 * (1 - t) + p2 * t

def mid(a, b):
    return (a + b) / 2

def id(x):
    return x



def change(obj):
    def f(cls):
        d = cls.__dict__
        d = {k: d[k] for k in d if not k.startswith("__")}
        for k, v in d.items():
            if callable(v):
                setattr(obj, k, partial(v, obj))
            else:
                setattr(obj, k, v)
        obj.super = d
        return obj
    return f

import builtins
builtins.__dict__["change"] = change

class Control:
    pass

def join(window):
    def f(x):
        if hasattr(x, "__iter__"):
            for a in x:
                try:
                    a >> window
                except Exception as e:
                    print(x, ">> error")
                    raise e
        else:
            try:
                x >> window
            except Exception as e:
                print(x, ">> error")
                raise e
    return f

# ~ def grid(x, y):
    # ~ return V2(x * 1.5, y * 0.5)

"""
class Walker(Control):
    def __init__(self, camera):
        self.camera = camera
        self.v = 3
        self.dirx = 0
        self.diry = 0
        self.dirz = 0

    def start(self, window, e):
        if e.keysym.sym == sdl2.SDLK_TAB:
            window.key_down_callback.remove(self.start)
            window.key_down_callback.append(self.end)
            window.mouse_move_callback.append(self.rotate)
            window.key_down_callback.append(self.speed_change1)
            window.key_up_callback.append(self.speed_change2)
            window.update_callback.append(self.move_update)

            sdl2.SDL_ShowCursor(0)
            # ~ sdl2.SDL_SetRelativeMouseMode(1)
            return True

    def end(self, window, e):
        if e.keysym.sym == sdl2.SDLK_TAB:
            window.key_down_callback.remove(self.end)
            window.key_down_callback.append(self.start)
            window.mouse_move_callback.remove(self.rotate)
            window.key_down_callback.remove(self.speed_change1)
            window.key_up_callback.remove(self.speed_change2)
            window.update_callback.remove(self.move_update)

            sdl2.SDL_ShowCursor(1)
            # ~ sdl2.SDL_SetRelativeMouseMode(0)
            return True

    def rotate(self, window, e):
        print(dir(e))
        dx, dy = e.rel
        os = window.os
        dx, dy = dx / os(1), dy / os(1)
        k = 0.2
        th1, th2 = dx * k, dy * k
        self.camera.rot = vrot(U3.up, - th1) * self.camera.rot    #x axis
        self.camera.rot =  self.camera.rot * vrot(U3.rt, -th2)      # y axis
        midx, midy = window.o((0, 0))


    def speed_change1(self, window, e):
        if e.key == pygame.K_a:
            self.dirx = -1
            return True
        elif e.key == pygame.K_d:
            self.dirx = 1
            return True
        elif e.key == pygame.K_w:
            self.diry = 1
            return True
        elif e.key == pygame.K_s:
            self.diry = -1
            return True
        elif e.key == pygame.K_SPACE:
            self.dirz = 1
            return True
        elif e.key == pygame.K_LSHIFT:
            self.dirz = -1
            return True

    def speed_change2(self, window, e):
        if e.key == pygame.K_a or e.key == pygame.K_d:
            self.dirx = 0
            return True
        elif e.key == pygame.K_w or e.key == pygame.K_s:
            self.diry = 0
            return True
        elif e.key == pygame.K_SPACE or e.key == pygame.K_LSHIFT:
            self.dirz = 0
            return True

    def move_update(self, window, dt):
        return
        v =  self.dirx * self.camera.right() + self.diry * (U3.up.cross(self.camera.right())) + \
            U3(0, 0, self.dirz)
        if v.length > 0:
            v = v.normalized
            self.camera.pos += v * self.v * dt



    def reg_event(self, window):
        window.key_down_callback.append(self.start)
"""


class Walker2(Control):         #  滚轮导航
    def __init__(self, camera):
        self.camera = camera

    def start(self, window, e):
        if e.keysym.sym == sdl2.SDLK_TAB:
            window.key_down_callback.remove(self.start)
            window.key_down_callback.append(self.end)
            window.mouse_move_callback.append(self.rotate)
            window.wheel_callback.append(self.wheel)

            sdl2.SDL_ShowCursor(0)
            sdl2.SDL_SetRelativeMouseMode(1)
            return True

    def end(self, window, e):
        if e.keysym.sym == sdl2.SDLK_TAB:
            window.key_down_callback.remove(self.end)
            window.key_down_callback.append(self.start)
            window.mouse_move_callback.remove(self.rotate)
            window.wheel_callback.remove(self.wheel)

            sdl2.SDL_ShowCursor(1)
            sdl2.SDL_SetRelativeMouseMode(0)
            return True

    def rotate(self, window, e):
        dx, dy = e.xrel, e.yrel
        os = window.os
        dx, dy = dx / os(1), dy / os(1)
        k = 0.2
        th1, th2 = dx * k, dy * k
        self.camera.t.walk(rot=vrot(U3.up, - th1))     #x axis
        self.camera.t.iwalk(rot=vrot(U3.rt, - th2))       # y axis
        return True

    def wheel(self, window, e):
        d = e.preciseY
        if d > 0:
            self.camera.t.iwalk(pos=U3.fd * 2)
        elif d < 0:
            self.camera.t.iwalk(pos=U3.bk * 2)
        return True


    def reg_event(self, window):
        window.key_down_callback.append(self.start)




class Walker3(Control):         #  滚轮导航     # 向前，而不是向正对方向
    def __init__(self, camera):
        self.camera = camera
        self.temp = None

    def down(self, window, e):
        if e.button == 3:
            # ~ print("down")
            window.mouse_move_callback.append(self.motion)
            window.mouse_up_callback.append(self.up)
            window.mouse_down_callback.remove(self.down)
            window.mouse_down_callback.remove(self.wheel_down)

            sdl2.SDL_ShowCursor(0)
            sdl2.SDL_SetRelativeMouseMode(1)
            self.temp = (e.x, e.y)
            return True

    def up(self, window, e):
        if e.button == 3:
            # ~ print("up")
            window.mouse_move_callback.remove(self.motion)
            window.mouse_up_callback.remove(self.up)
            window.mouse_down_callback.append(self.down)
            window.mouse_down_callback.append(self.wheel_down)

            sdl2.SDL_ShowCursor(1)
            sdl2.SDL_SetRelativeMouseMode(0)
            sdl2.SDL_WarpMouseInWindow(window.window, self.temp[0], self.temp[1])
            self.temp = None
            return True

    def motion(self, window, e):
        dx, dy = e.xrel, e.yrel
        os = window.os
        dx, dy = dx / os(1), dy / os(1)
        k = 0.2
        th1, th2 = dx * k, dy * k
        self.camera.t.walk(rot=vrot(U3.up, - th1))     #x axis
        self.camera.t.iwalk(rot=vrot(U3.rt, - th2))       # y axis
        return True


    def wheel_down(self, window, e):
        if e.button == 2:
            # ~ print("wheel down")
            window.mouse_move_callback.append(self.wheel_motion)
            window.mouse_up_callback.append(self.wheel_up)
            window.mouse_down_callback.remove(self.down)
            window.mouse_down_callback.remove(self.wheel_down)

            sdl2.SDL_ShowCursor(0)
            sdl2.SDL_SetRelativeMouseMode(1)
            self.temp = (e.x, e.y)
            return True

    def wheel_up(self, window, e):
        if e.button == 2:
            # ~ print("wheel up")
            window.mouse_move_callback.remove(self.wheel_motion)
            window.mouse_up_callback.remove(self.wheel_up)
            window.mouse_down_callback.append(self.down)
            window.mouse_down_callback.append(self.wheel_down)

            sdl2.SDL_ShowCursor(1)
            sdl2.SDL_SetRelativeMouseMode(0)
            # ~ sdl2.SDL_WarpMouseInWindow(window.window, self.temp[0], self.temp[1])
            self.temp = None
            return True

    def wheel_motion(self, window, e):
        dx, dy = e.xrel, e.yrel
        os = window.os
        dx, dy = dx / os(1), dy / os(1)
        k = 2
        mx, my = dx * k, dy * k
        self.camera.t.iwalk(pos=-U3.rt * mx)
        self.camera.t.walk(pos=U3.up * my)
        return True


    def wheel(self, window, e):
        d = e.preciseY
        if d > 0:
            self.camera.t.walk(pos=U3.up.cross(self.camera.t.rt) * 2)
        elif d < 0:
            self.camera.t.walk(pos=U3.up.cross(self.camera.t.lt) * 2)
        return True


    def reg_event(self, window):
        window.mouse_down_callback.append(self.down)
        window.mouse_down_callback.append(self.wheel_down)
        window.wheel_callback.append(self.wheel)





class Walker3b(Control):
    def __init__(self, camera):
        self.camera = camera
        self.temp = None

    def down(self, window, e):
        if e.button == 3:
            # ~ print("down")
            window.mouse_move_callback.append(self.motion)
            window.mouse_up_callback.append(self.up)
            window.mouse_down_callback.remove(self.down)
            window.mouse_down_callback.remove(self.wheel_down)

            sdl2.SDL_ShowCursor(0)
            sdl2.SDL_SetRelativeMouseMode(1)
            self.temp = (e.x, e.y)
            return True

    def up(self, window, e):
        if e.button == 3:
            # ~ print("up")
            window.mouse_move_callback.remove(self.motion)
            window.mouse_up_callback.remove(self.up)
            window.mouse_down_callback.append(self.down)
            window.mouse_down_callback.append(self.wheel_down)

            sdl2.SDL_ShowCursor(1)
            sdl2.SDL_SetRelativeMouseMode(0)
            sdl2.SDL_WarpMouseInWindow(window.window, self.temp[0], self.temp[1])
            self.temp = None
            return True

    def motion(self, window, e):
        dx, dy = e.xrel, e.yrel
        os = window.os
        dx, dy = dx / os(1), dy / os(1)
        k = 0.2
        th1, th2 = dx * k, dy * k
        self.camera.t.walk(rot=vrot(U3.up, - th1))     #x axis
        self.camera.t.iwalk(rot=vrot(U3.rt, - th2))       # y axis
        return True


    def wheel_down(self, window, e):
        if e.button == 2:
            # ~ print("wheel down")
            window.mouse_move_callback.append(self.wheel_motion)
            window.mouse_up_callback.append(self.wheel_up)
            window.mouse_down_callback.remove(self.down)
            window.mouse_down_callback.remove(self.wheel_down)

            sdl2.SDL_ShowCursor(0)
            sdl2.SDL_SetRelativeMouseMode(1)
            self.temp = (e.x, e.y)
            return True

    def wheel_up(self, window, e):
        if e.button == 2:
            # ~ print("wheel up")
            window.mouse_move_callback.remove(self.wheel_motion)
            window.mouse_up_callback.remove(self.wheel_up)
            window.mouse_down_callback.append(self.down)
            window.mouse_down_callback.append(self.wheel_down)

            sdl2.SDL_ShowCursor(1)
            sdl2.SDL_SetRelativeMouseMode(0)
            # ~ sdl2.SDL_WarpMouseInWindow(window.window, self.temp[0], self.temp[1])
            self.temp = None
            return True

    def wheel_motion(self, window, e):
        dx, dy = e.xrel, e.yrel
        os = window.os
        dx, dy = dx / os(1), dy / os(1)
        k = 1
        mx, my = dx * k, dy * k
        self.camera.t.iwalk(pos=-U3.rt * mx)
        self.camera.t.iwalk(pos=U3.up * my)
        return True


    def wheel(self, window, e):
        d = e.preciseY
        if d > 0:
            self.camera.t.iwalk(pos=U3.fd * 2)
        elif d < 0:
            self.camera.t.iwalk(pos=U3.bk * 2)
        return True


    def reg_event(self, window):
        window.mouse_down_callback.append(self.down)
        window.mouse_down_callback.append(self.wheel_down)
        window.wheel_callback.append(self.wheel)



class Viewer(Control):
    def __init__(self, camera):
        self.camera = camera
        self.temp = {}


    def motion(self, window, e):
        os = window.os
        dx, dy = e.xrel, e.yrel
        dx, dy = dx / os(1), dy / os(1)
        k = 0.4
        th1, th2 = dx * k, dy * k
        le = length(self.camera.t.pos)
        self.camera.t.pos = U3(0,0,0)
        self.camera.t.walk(rot=vrot(U3.up,  -th1))
        self.camera.t.iwalk(rot=vrot(U3.rt, -th2))
        self.camera.t.iwalk(U3.fd * -le)


    def wheel(self, window, e):
        d = e.preciseY
        if d > 0:
            self.camera.t.iwalk(pos=U3.fd * 0.6)
        elif d < 0:
            self.camera.t.iwalk(pos=U3.bk * 0.6)
        return True


    def down(self, window, e):
        if e.button == 3:
            # ~ print("down")
            window.mouse_move_callback.append(self.motion)
            window.mouse_up_callback.append(self.up)
            window.mouse_down_callback.remove(self.down)

            sdl2.SDL_ShowCursor(0)
            sdl2.SDL_SetRelativeMouseMode(1)
            self.temp = (e.x, e.y)
            return True

    def up(self, window, e):
        if e.button == 3:
            # ~ print("up")
            window.mouse_move_callback.remove(self.motion)
            window.mouse_up_callback.remove(self.up)
            window.mouse_down_callback.append(self.down)

            sdl2.SDL_ShowCursor(1)
            sdl2.SDL_SetRelativeMouseMode(0)
            # ~ sdl2.SDL_WarpMouseInWindow(window.window, self.temp[0], self.temp[1])
            self.temp = None
            return True



    def reg_event(self, window):
        window.mouse_down_callback.append(self.down)
        window.wheel_callback.append(self.wheel)

        m = pyrr.matrix44.create_look_at(self.camera.t.pos, U3(0, 0, 0), U3.up)
        self.camera.t.rot = Quaternion.from_matrix(m)

    def unreg_event(self, window):
        window.wheel_callback.remove(self.wheel)
        if self.down in window.mouse_down_callback:
            window.mouse_down_callback.remove(self.down)
        else:
            window.mouse_move_callback.remove(self.motion)
            window.mouse_up_callback.remove(self.up)
            sdl2.SDL_ShowCursor(1)
            sdl2.SDL_SetRelativeMouseMode(0)
            self.temp = None





